import tkinter as tk
import ttkbootstrap as ttk
from ttkbootstrap.constants import *
from tkinter import messagebox, filedialog
import os
import json
import re

class LineNumberCanvas(tk.Canvas):
    def __init__(self, *args, **kwargs):
        self.text_widget = None
        self.style = kwargs.pop('style', None)  # 从kwargs中获取style
        super().__init__(*args, **kwargs)
        self.config(width=45)  # 增加宽度以适应更多位数

    def attach(self, text_widget):
        self.text_widget = text_widget
        self.redraw()

    def redraw(self, *args):
        self.delete("all")

        if not self.text_widget:
            return

        # 获取可见区域的第一行和最后一行
        first_line = self.text_widget.index("@0,0")
        last_line = self.text_widget.index("@0,%d" % self.winfo_height())

        # 转换为行号
        first = int(float(first_line))
        last = int(float(last_line)) + 1

        # 设置字体和颜色
        fill_color = self.style.colors.get('info') if self.style else 'gray'
        font = ('Consolas', 10)  # 使用等宽字体

        # 绘制行号
        for line_num in range(first, last):
            # 获取每行的y坐标
            dline = self.text_widget.dlineinfo("%d.0" % line_num)
            if dline:  # 如果行可见
                y = dline[1]  # 获取y坐标
                # 右对齐显示行号
                self.create_text(40, y, anchor="ne",
                               text=str(line_num),
                               font=font,
                               fill=fill_color)

    def _on_text_modified(self, event=None):
        # 文本内容变化时更新行号
        self.redraw()

    def _on_scroll(self, *args):
        # 滚动时更新行号
        self.redraw()

class SearchBar(ttk.Frame):
    def __init__(self, master, text_widget, *args, **kwargs):
        super().__init__(master, *args, **kwargs)
        self.text_widget = text_widget

        # 搜索输入框
        self.search_var = tk.StringVar()
        self.search_entry = ttk.Entry(self, textvariable=self.search_var)
        self.search_entry.pack(side=tk.LEFT, padx=5)

        # 替换输入框
        self.replace_var = tk.StringVar()
        self.replace_entry = ttk.Entry(self, textvariable=self.replace_var)
        self.replace_entry.pack(side=tk.LEFT, padx=5)

        # 按钮
        ttk.Button(self, text="查找下一个",
                  command=self.find_next, style='info.Outline.TButton').pack(side=tk.LEFT, padx=2)
        ttk.Button(self, text="替换",
                  command=self.replace, style='info.Outline.TButton').pack(side=tk.LEFT, padx=2)
        ttk.Button(self, text="全部替换",
                  command=self.replace_all, style='info.Outline.TButton').pack(side=tk.LEFT, padx=2)
        ttk.Button(self, text="关闭",
                  command=self.hide, style='danger.Outline.TButton').pack(side=tk.LEFT, padx=2)

        self.pack_forget()  # 初始隐藏

    def show(self):
        self.pack(side=tk.TOP, fill=tk.X, padx=5, pady=2)
        self.search_entry.focus_set()

    def hide(self):
        self.pack_forget()
        self.text_widget.tag_remove('search', '1.0', tk.END)
        self.text_widget.focus_set()

    def find_next(self):
        # 移除之前的搜索高亮
        self.text_widget.tag_remove('search', '1.0', tk.END)

        search_text = self.search_var.get()
        if search_text:
            pos = self.text_widget.search(search_text, tk.INSERT, tk.END)
            if not pos:  # 如果没找到，从头开始找
                pos = self.text_widget.search(search_text, '1.0', tk.END)

            if pos:
                line, char = pos.split('.')
                end_pos = f"{line}.{int(char) + len(search_text)}"
                self.text_widget.tag_add('search', pos, end_pos)
                self.text_widget.tag_config('search', background='red')
                self.text_widget.see(pos)
                self.text_widget.mark_set(tk.INSERT, end_pos)

    def replace(self):
        search_text = self.search_var.get()
        replace_text = self.replace_var.get()

        if search_text:
            # 获取选中的文本范围
            try:
                start = self.text_widget.tag_ranges('search')[0]
                end = self.text_widget.tag_ranges('search')[1]
                # 替换文本
                self.text_widget.delete(start, end)
                self.text_widget.insert(start, replace_text)
                # 查找下一个
                self.find_next()
            except IndexError:
                self.find_next()

    def replace_all(self):
        search_text = self.search_var.get()
        replace_text = self.replace_var.get()

        if search_text:
            content = self.text_widget.get('1.0', tk.END)
            new_content = content.replace(search_text, replace_text)
            self.text_widget.delete('1.0', tk.END)
            self.text_widget.insert('1.0', new_content)

class StatusBar(ttk.Frame):
    def __init__(self, master, text_widget, *args, **kwargs):
        super().__init__(master, *args, **kwargs)
        self.text_widget = text_widget

        # 创建状态标签
        self.position_label = ttk.Label(self, text="行: 1 列: 1")
        self.position_label.pack(side=tk.RIGHT, padx=5)

        self.length_label = ttk.Label(self, text="长度: 0")
        self.length_label.pack(side=tk.RIGHT, padx=5)

        # 绑定文本更改事件
        self.text_widget.bind('<KeyRelease>', self.update_status)
        self.text_widget.bind('<Button-1>', self.update_status)

    def update_status(self, event=None):
        # 更新光标位置
        pos = self.text_widget.index(tk.INSERT)
        line, col = pos.split('.')
        self.position_label.config(text=f"行: {line} 列: {int(col)+1}")

        # 更新文本长度
        content = self.text_widget.get('1.0', tk.END)
        length = len(content) - 1  # 减去最后的换行符
        self.length_label.config(text=f"长度: {length}")

class OutlineFrame(ttk.Frame):
    def __init__(self, master, text_widget, *args, **kwargs):
        super().__init__(master, *args, **kwargs)
        self.text_widget = text_widget

        # 创建大纲树
        self.outline_tree = ttk.Treeview(self, show="tree",
                                       selectmode="browse",
                                       bootstyle="info",
                                       height=25)  # 设置初始高度
        self.outline_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

        # 添加滚动条
        scrollbar = ttk.Scrollbar(self, orient="vertical",
                                command=self.outline_tree.yview)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        self.outline_tree.configure(yscrollcommand=scrollbar.set)

        # 绑定点击事件
        self.outline_tree.bind('<<TreeviewSelect>>', self.on_select)

        # 绑定文本更新事件
        self.text_widget.bind('<<Modified>>', self.update_outline)

    def update_outline(self, event=None):
        # 清除修改标记
        self.text_widget.edit_modified(False)

        # 清空大纲树
        self.outline_tree.delete(*self.outline_tree.get_children())

        # 获取文本内容
        content = self.text_widget.get("1.0", tk.END)

        # 查找所有标题
        lines = content.split('\n')
        current_levels = [None] * 6  # 用于跟踪各级标题的最后一个节点

        for i, line in enumerate(lines, 1):
            # 检查Markdown标题格式
            md_match = re.match(r'^(#{1,6})\s+(.+)$', line)
            if md_match:
                level = len(md_match.group(1))
                title = md_match.group(2)

                # 确定父节点
                parent = ''
                if level > 1:
                    for l in range(level-1, 0, -1):
                        if current_levels[l-1]:
                            parent = current_levels[l-1]
                            break

                # 插入节点
                item_id = self.outline_tree.insert(parent, 'end',
                                                 text=title,
                                                 values=(f"{i}.0",))
                current_levels[level-1] = item_id

                # 清除所有下级标题的跟踪
                for l in range(level, 6):
                    current_levels[l] = None

            # 检查其他标题格式（如：=== 或 ---）
            elif i > 1 and (line.startswith('===') or line.startswith('---')):
                title = lines[i-2].strip()
                level = 1 if line.startswith('===') else 2

                # 插入节点
                item_id = self.outline_tree.insert('', 'end',
                                                 text=title,
                                                 values=(f"{i-1}.0",))
                current_levels[level-1] = item_id

                # 清除所有下级标题的跟踪
                for l in range(level, 6):
                    current_levels[l] = None

    def on_select(self, event):
        # 获取选中的项
        selection = self.outline_tree.selection()
        if selection:
            item = selection[0]
            # 获取行号
            line_pos = self.outline_tree.item(item)['values'][0]
            # 跳转到对应位置
            self.text_widget.see(line_pos)
            self.text_widget.mark_set(tk.INSERT, line_pos)
            self.text_widget.focus_set()

class TextEditor:
    def __init__(self, root):
        # 添加撤销栈配置（移到最前面）
        self.undo_stack_size = 1000  # 最大撤销次数
        self.compound_undo_delay = 1000  # 合并撤销操作的时间间隔（毫秒）

        self.root = root
        self.root.title("个人知识管理")
        self.current_file = None

        # 配置文件路径
        self.config_file = os.path.join(os.path.expanduser('~'), '.noteedit_config.json')

        # 加载配置
        self.load_config()

        # 设置主题样式
        self.style = ttk.Style()

        # 配置标签页基本样式
        self.style.configure(
            'Custom.TNotebook',
            background=self.style.colors.get('dark')  # 设置notebook背景色
        )

        # 配置普通标签页样式
        self.style.configure(
            'Custom.TNotebook.Tab',
            padding=[12, 6],  # 增加内边距
            background=self.style.colors.get('secondary'),  # 未选中时的背景色
            foreground=self.style.colors.get('light'),     # 文字颜色
            lightcolor=self.style.colors.get('secondary'), # 高亮色
            borderwidth=1,  # 边框宽度
            font=('Helvetica', 10)  # 设置字体
        )

        # 配置选中标签页的样式
        self.style.map(
            'Custom.TNotebook.Tab',
            background=[
                ('selected', self.style.colors.get('primary')),  # 选中时的背景色
                ('active', self.style.colors.get('info'))       # 鼠标悬停时的背景色
            ],
            foreground=[
                ('selected', self.style.colors.get('light')),   # 选中时的文字颜色
                ('active', self.style.colors.get('light'))      # 鼠标悬停时的文字颜色
            ],
            expand=[('selected', [1, 1, 1, 0])]  # 选中时的扩展效果
        )

        # 配置未保存文件的标签页样式
        self.style.configure(
            'Warning.Custom.TNotebook.Tab',
            background=self.style.colors.get('warning'),    # 警告背景色
            foreground=self.style.colors.get('dark'),      # 深色文字
            lightcolor=self.style.colors.get('warning')    # 警告高亮色
        )

        # 配置未保存文件选中时的样式
        self.style.map(
            'Warning.Custom.TNotebook.Tab',
            background=[
                ('selected', self.style.colors.get('danger')),  # 选中时的警告色
                ('active', self.style.colors.get('warning'))    # 鼠标悬停时的警告色
            ],
            foreground=[
                ('selected', self.style.colors.get('light')),   # 选中时的文字颜色
                ('active', self.style.colors.get('dark'))       # 鼠标悬停时的文字颜色
            ]
        )

        # 初始化标签页字典
        self.tabs = {}

        # 创建主框架
        self.main_frame = ttk.Frame(self.root)
        self.main_frame.pack(fill=tk.BOTH, expand=True)

        # 创建左侧文件浏览器
        self.create_file_explorer()

        # 创建标签页管理器
        self.create_notebook()

        # 创建菜单
        self.create_menu()

        # 绑定关闭窗口事件
        self.root.protocol("WM_DELETE_WINDOW", self.on_closing)

    def load_config(self):
        """加载配置文件"""
        self.config = {
            'last_directory': os.getcwd()  # 默认为当前目录
        }
        try:
            if os.path.exists(self.config_file):
                with open(self.config_file, 'r', encoding='utf-8') as f:
                    saved_config = json.load(f)
                    self.config.update(saved_config)

                # 如果保存的目录存在，则切换到该目录
                if os.path.exists(self.config['last_directory']):
                    os.chdir(self.config['last_directory'])
        except Exception as e:
            print(f"加载配置文件失败: {str(e)}")

    def save_config(self):
        """保存配置文件"""
        try:
            self.config['last_directory'] = os.getcwd()
            with open(self.config_file, 'w', encoding='utf-8') as f:
                json.dump(self.config, f, ensure_ascii=False, indent=4)
        except Exception as e:
            print(f"保存配置文件失败: {str(e)}")

    def on_closing(self):
        """窗口关闭时的处理"""
        # 检查是否有未保存的更改
        for tab_id in list(self.tabs.keys()):
            self.notebook.select(self.tabs[tab_id]['frame'])
            if self.has_unsaved_changes(tab_id):
                if not self.ask_save_changes():
                    return

        # 保存配置
        self.save_config()

        # 关闭窗口
        self.root.destroy()

    def create_file_explorer(self):
        # 创建左侧面板框架
        self.left_panel = ttk.Frame(self.main_frame, width=200)
        self.left_panel.pack(side=tk.LEFT, fill=tk.Y, padx=5, pady=5)
        self.left_panel.pack_propagate(False)  # 防止框架被压缩

        # 创建文件浏览器框架
        explorer_frame = ttk.LabelFrame(self.left_panel, text="文件浏览器")
        explorer_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 5))

        # 添加当前路径标签
        self.path_label = ttk.Label(explorer_frame, wraplength=180,
                                  justify=tk.LEFT, style='info.TLabel')
        self.path_label.pack(fill=tk.X, padx=2, pady=(0, 5))

        # 创建文件树框架
        tree_frame = ttk.Frame(explorer_frame)
        tree_frame.pack(fill=tk.BOTH, expand=True)

        # 添加文件树
        self.file_tree = ttk.Treeview(tree_frame, bootstyle="info",
                                    selectmode="browse",
                                    show="tree",
                                    height=15)  # 设置初始高度
        self.file_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

        # 设置文件树样式
        self.file_tree.tag_configure('folder', foreground=self.style.colors.get('warning'))
        self.file_tree.tag_configure('file', foreground=self.style.colors.get('info'))

        # 添加双击事件绑定
        self.file_tree.bind("<Double-1>", self.on_tree_double_click)

        # 添加滚动条
        scrollbar = ttk.Scrollbar(tree_frame, orient="vertical",
                                command=self.file_tree.yview)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        self.file_tree.configure(yscrollcommand=scrollbar.set)

        # 创建文件树右键菜单
        self.file_tree_menu = ttk.Menu(self.file_tree, tearoff=0)
        self.file_tree_menu.add_command(label="删除", command=self.delete_selected_file)

        # 绑定右键事件
        self.file_tree.bind("<Button-3>", self.show_file_tree_menu)

        # 创建大纲框架
        self.outline_frame = ttk.LabelFrame(self.left_panel, text="大纲")
        self.outline_frame.pack(fill=tk.BOTH, expand=True)

        # 初始化文件树
        self.update_file_tree()

    def create_notebook(self):
        """创建标签页管理器"""
        self.notebook = ttk.Notebook(
            self.main_frame,
            style='Custom.TNotebook'  # 使用自定义样式
        )
        self.notebook.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)

        # 绑定右键菜单
        self.tab_menu = ttk.Menu(self.notebook)
        self.tab_menu.add_command(label="关闭", command=self.close_current_tab)
        self.tab_menu.add_command(label="关闭其他", command=self.close_other_tabs)
        self.tab_menu.add_command(label="关闭所有", command=self.close_all_tabs)

        # 绑定右键事件
        self.notebook.bind("<Button-3>", self.show_tab_menu)

        # 创建第一个标签页
        self.new_tab()

    def new_tab(self, title="新标签页"):
        # 创建新的文本编辑区框架
        frame = ttk.Frame(self.notebook)

        # 创建垂直布局框架
        v_frame = ttk.Frame(frame)
        v_frame.pack(fill=tk.BOTH, expand=True)

        # 创建搜索栏 (移到顶部)
        search_bar = SearchBar(v_frame, None)  # 暂时传入None

        # 创建水平布局框架
        h_frame = ttk.Frame(v_frame)
        h_frame.pack(fill=tk.BOTH, expand=True)

        # 创建行号画布
        line_numbers = LineNumberCanvas(h_frame,
                                     bg=self.style.colors.get('light'),
                                     style=self.style)
        line_numbers.pack(side=tk.LEFT, fill=tk.Y)

        # 创建中间编辑区框架
        edit_frame = ttk.Frame(h_frame)
        edit_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

        # 创建文本编辑区和滚动条的容器
        text_container = ttk.Frame(edit_frame)
        text_container.pack(fill=tk.BOTH, expand=True)

        # 创建文本编辑区
        text_area = tk.Text(text_container, wrap=tk.WORD,
                          font=('Consolas', 11),
                          bg=self.style.colors.get('light'),
                          fg=self.style.colors.get('dark'),
                          insertbackground=self.style.colors.get('dark'),
                          undo=True,  # 启用撤销功能
                          maxundo=self.undo_stack_size,  # 设置最大撤销次数
                          autoseparators=True)  # 自动添加撤销分隔符
        text_area.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

        # 添加撤销相关变量
        text_area.last_edit_time = 0
        text_area.edit_separator_pending = None

        # 绑定按键事件用于优化撤销操作
        text_area.bind('<Key>', lambda e: self.handle_key_event(e, text_area))
        text_area.bind('<Control-Key>', lambda e: self.handle_control_key(e, text_area))

        # 添加文本区域的滚动条
        text_scrollbar = ttk.Scrollbar(text_container, orient="vertical",
                                     command=text_area.yview)
        text_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        text_area.config(yscrollcommand=text_scrollbar.set)

        # 创建大纲视图（移到左侧面板）
        outline = OutlineFrame(self.outline_frame, text_area)
        outline.pack(fill=tk.BOTH, expand=True, padx=2, pady=2)

        # 初始时隐藏大纲视图
        outline.pack_forget()

        # 更新搜索栏的text_widget引用
        search_bar.text_widget = text_area

        # 绑定行号更新事件
        text_area.bind('<<Modified>>', lambda e: [
            line_numbers._on_text_modified(e),
            outline.update_outline(e),
            self.on_text_modified(e)
        ])
        text_area.bind('<Configure>', line_numbers.redraw)
        text_area.bind('<KeyPress>', line_numbers.redraw)
        text_area.bind('<KeyRelease>', line_numbers.redraw)
        text_area.bind('<MouseWheel>', line_numbers._on_scroll)

        # 绑定垂直滚动事件
        def on_vertical_scroll(*args):
            line_numbers.redraw()
        text_area.bind('<ButtonRelease-1>', on_vertical_scroll)
        text_area.bind('<B1-Motion>', on_vertical_scroll)

        # 初始化行号显示
        line_numbers.attach(text_area)
        line_numbers.redraw()

        # 创建状态栏
        status_bar = StatusBar(frame, text_area)
        status_bar.pack(side=tk.BOTTOM, fill=tk.X)

        # 添加新标签页
        self.notebook.add(frame, text=title)

        # 保存标签页信息
        tab_id = len(self.tabs)
        self.tabs[tab_id] = {
            'frame': frame,
            'text_area': text_area,
            'search_bar': search_bar,
            'file_path': None,
            'outline': outline  # 保存大纲视图的引用
        }

        # 绑定标签页切换事件
        self.notebook.bind('<<NotebookTabChanged>>', self.on_tab_changed)

        # 绑定快捷键
        text_area.bind('<Control-f>', lambda e: search_bar.show())
        text_area.bind('<Escape>', lambda e: search_bar.hide())

        # 添加保存、撤销、重做快捷键
        text_area.bind('<<Save>>', lambda e: self.handle_save(e))
        text_area.bind('<<Undo>>', lambda e: self.handle_undo(e))
        text_area.bind('<<Redo>>', lambda e: self.handle_redo(e))
        text_area.bind('<<OpenDir>>', lambda e: self.handle_open_directory(e))
        text_area.bind('<<Preview>>', lambda e: self.handle_preview(e))

        # 绑定虚拟事件到实际按键（只使用 Ctrl）
        text_area.event_add('<<Save>>', '<Control-s>')
        text_area.event_add('<<Undo>>', '<Control-z>')
        text_area.event_add('<<Redo>>', '<Control-y>')
        text_area.event_add('<<OpenDir>>', '<Control-o>')
        text_area.event_add('<<Preview>>', '<Control-p>')

        # 绑定快捷键
        text_area.bind('<Tab>', self.handle_tab)  # 添加Tab键绑定
        text_area.bind('<Tab>', lambda e: self.indent_selected_lines(e))  # 添加这行

        # 切换到新标签页
        self.notebook.select(frame)
        return tab_id

    def get_current_tab(self):
        current = self.notebook.select()
        for tab_id, tab_info in self.tabs.items():
            if str(tab_info['frame']) == str(current):
                return tab_id, tab_info
        return None, None

    def open_directory(self):
        dir_path = filedialog.askdirectory()
        if dir_path:
            os.chdir(dir_path)
            self.update_file_tree()
            # 保存新的工作目录到配置
            self.save_config()

    def on_tree_double_click(self, event):
        """处理文件树双击事件"""
        item_id = self.file_tree.selection()[0]
        file_path = self.get_item_path(item_id)

        if os.path.isfile(file_path):
            # 使用 open_file 方法打开文件，它会处理重复打开的情况
            self.open_file(file_path=file_path)

    def get_item_path(self, item_id):
        # 去掉图标（前两个字符）
        item_text = self.file_tree.item(item_id)['text']
        clean_text = item_text[2:]  # 跳过图标字符
        path_parts = [clean_text]

        parent = self.file_tree.parent(item_id)
        while parent:
            parent_text = self.file_tree.item(parent)['text']
            clean_parent_text = parent_text[2:]  # 跳过图标字符
            path_parts.insert(0, clean_parent_text)
            parent = self.file_tree.parent(parent)

        return os.path.join(os.getcwd(), *path_parts)

    def open_file(self, file_path=None):
        """打开文件"""
        if not file_path:
            file_path = filedialog.askopenfilename(
                filetypes=[("文本文件", "*.txt"), ("所有文件", "*.*")]
            )

        if file_path:
            # 检查文件是否已经打开
            for tab_id, tab_info in self.tabs.items():
                if tab_info['file_path'] == file_path:
                    # 如果文件已经打开，切换到对应标签页
                    self.notebook.select(tab_info['frame'])
                    return

            try:
                with open(file_path, 'r', encoding='utf-8') as file:
                    content = file.read()

                # 创建新标签页
                self.new_tab(os.path.basename(file_path))

                # 获取当前标签页信息
                tab_id, tab_info = self.get_current_tab()

                # 设置文本内容
                tab_info['text_area'].delete(1.0, tk.END)
                tab_info['text_area'].insert(1.0, content)

                # 保存文件路径
                tab_info['file_path'] = file_path

            except Exception as e:
                messagebox.showerror("错误", f"无法打开文件: {str(e)}")

    def save_file(self):
        tab_id, tab_info = self.get_current_tab()
        if tab_id is None:
            return

        if tab_info['file_path']:
            try:
                content = tab_info['text_area'].get(1.0, tk.END)
                with open(tab_info['file_path'], 'w', encoding='utf-8') as file:
                    file.write(content)
                # 移除标题中的*号
                current_title = self.notebook.tab(tab_info['frame'], "text")
                if current_title.startswith("*"):
                    self.notebook.tab(tab_info['frame'], text=current_title[1:])
                    # 恢复标签页样式
                    tab_info['frame'].configure(style='Custom.TNotebook.Tab')
                # 刷新文件树
                self.update_file_tree()
            except Exception as e:
                messagebox.showerror("错误", f"无法保存文件: {str(e)}")
        else:
            self.save_file_as()

    def save_file_as(self):
        tab_id, tab_info = self.get_current_tab()
        if tab_id is None:
            return

        file_path = filedialog.asksaveasfilename(
            defaultextension=".txt",
            filetypes=[("文本文件", "*.txt"), ("所有文件", "*.*")]
        )
        if file_path:
            tab_info['file_path'] = file_path
            # 更新标签页标题（不带*号）
            self.notebook.tab(tab_info['frame'], text=os.path.basename(file_path))
            self.save_file()

    def new_file(self):
        """创建新的空白标签页"""
        self.new_tab("未命名")

    def create_menu(self):
        menubar = tk.Menu(self.root)
        self.root.config(menu=menubar)

        # 文件菜单
        file_menu = tk.Menu(menubar, tearoff=0)
        menubar.add_cascade(label="文件", menu=file_menu)
        file_menu.add_command(label="新建", command=self.new_file)
        file_menu.add_command(label="打开", command=self.open_file)
        file_menu.add_command(label="打开目录 (Ctrl+O)", command=self.open_directory)
        file_menu.add_separator()
        file_menu.add_command(label="保存 (Ctrl+S)", command=self.save_file)
        file_menu.add_command(label="另存为", command=self.save_file_as)  # 移除快捷键提示
        file_menu.add_separator()
        file_menu.add_command(label="退出", command=self.root.quit)

        # 编辑菜单
        edit_menu = tk.Menu(menubar, tearoff=0)
        menubar.add_cascade(label="编辑", menu=edit_menu)
        edit_menu.add_command(label="撤销 (Ctrl+Z)",
                            command=lambda: self.get_current_text_widget().edit_undo())
        edit_menu.add_command(label="重做 (Ctrl+Y)",
                            command=lambda: self.get_current_text_widget().edit_redo())
        edit_menu.add_separator()
        edit_menu.add_command(label="缩进 (Tab)",
                            command=lambda: self.indent_selected_lines())
        edit_menu.add_separator()
        edit_menu.add_command(label="查找 (Ctrl+F)",
                            command=lambda: self.get_current_search_bar().show())

    def get_current_text_widget(self):
        """获取当前标签页的文本组件"""
        _, tab_info = self.get_current_tab()
        return tab_info['text_area'] if tab_info else None

    def get_current_search_bar(self):
        """获取当前标签页的搜索栏"""
        _, tab_info = self.get_current_tab()
        return tab_info['search_bar'] if tab_info else None

    def update_file_tree(self):
        # 更新路径标签
        current_dir = os.getcwd()
        self.path_label.config(text=current_dir)

        # 清空现有项目
        for item in self.file_tree.get_children():
            self.file_tree.delete(item)

        # 添加文件和文件夹
        self.populate_tree("", current_dir)

    def populate_tree(self, parent, path):
        try:
            # 获取目录内容并排序
            items = os.listdir(path)
            # 分离文件夹和文件
            folders = [item for item in items if os.path.isdir(os.path.join(path, item))]
            files = [item for item in items if os.path.isfile(os.path.join(path, item))]

            # 按名称排序
            folders.sort(key=str.lower)
            files.sort(key=str.lower)

            # 先添加文件夹
            for item in folders:
                item_path = os.path.join(path, item)
                folder_icon = "📁 "  # 文件夹图标
                item_id = self.file_tree.insert(parent, "end",
                                              text=folder_icon + item,
                                              tags=('folder',))
                # 递归添加子目录
                self.populate_tree(item_id, item_path)

            # 再添加文件
            for item in files:
                file_icon = "📄 "  # 文件图标
                # 根据文件扩展名设置不同图标
                ext = os.path.splitext(item)[1].lower()
                if ext in ['.txt', '.md', '.log']:
                    file_icon = "📝 "
                elif ext in ['.py', '.java', '.cpp', '.js']:
                    file_icon = "👨‍💻 "
                elif ext in ['.jpg', '.png', '.gif', '.bmp']:
                    file_icon = "🖼️ "
                elif ext in ['.mp3', '.wav', '.ogg']:
                    file_icon = "🎵 "
                elif ext in ['.mp4', '.avi', '.mov']:
                    file_icon = "🎬 "

                self.file_tree.insert(parent, "end",
                                    text=file_icon + item,
                                    tags=('file',))
        except PermissionError:
            pass

    def show_tab_menu(self, event):
        """显示标签页右键菜单"""
        try:
            # 获取点击位置的标签页
            clicked_tab = self.notebook.tk.call(
                self.notebook._w, "identify", "tab", event.x, event.y
            )
            if clicked_tab is not None:
                # 选中被点击的标签页
                self.notebook.select(clicked_tab)
                # 显示菜单
                self.tab_menu.tk_popup(event.x_root, event.y_root)
        except:
            pass
        finally:
            # 确保菜单正确隐藏
            self.tab_menu.grab_release()

    def close_current_tab(self):
        current_tab = self.notebook.select()
        if current_tab:
            # 检查是否有未保存的更改
            tab_id, tab_info = self.get_current_tab()
            if tab_info:
                # 只有在有未保存更改时才提示保存
                if self.has_unsaved_changes(tab_id):
                    if not self.ask_save_changes():
                        return

                # 清理大纲视图
                if 'outline' in tab_info:
                    tab_info['outline'].pack_forget()
                    tab_info['outline'].destroy()

                # 从notebook和tabs字典中移除标签页
                self.notebook.forget(current_tab)
                if tab_id is not None:
                    del self.tabs[tab_id]

                # 如果没有标签页了，创建一个新的
                if len(self.tabs) == 0:
                    self.new_tab()

    def close_other_tabs(self):
        current_tab = self.notebook.select()
        if current_tab:
            current_tab_id = None
            # 找到当前标签页的ID
            for tab_id, tab_info in self.tabs.items():
                if str(tab_info['frame']) == str(current_tab):
                    current_tab_id = tab_id
                    break

            # 关闭其他标签页
            tabs_to_remove = []
            for tab_id, tab_info in self.tabs.items():
                if tab_id != current_tab_id:
                    if self.has_unsaved_changes(tab_id):
                        self.notebook.select(tab_info['frame'])
                        if not self.ask_save_changes():
                            continue
                    # 清理大纲视图
                    if 'outline' in tab_info:
                        tab_info['outline'].pack_forget()
                        tab_info['outline'].destroy()
                    tabs_to_remove.append(tab_id)

            # 移除标签页
            for tab_id in tabs_to_remove:
                self.notebook.forget(self.tabs[tab_id]['frame'])
                del self.tabs[tab_id]

    def close_all_tabs(self):
        # 检查所有标签页是否有未保存的更改
        for tab_id in list(self.tabs.keys()):
            self.notebook.select(self.tabs[tab_id]['frame'])
            if self.has_unsaved_changes(tab_id):
                if not self.ask_save_changes():
                    continue

            # 清理大纲视图
            if 'outline' in self.tabs[tab_id]:
                self.tabs[tab_id]['outline'].pack_forget()
                self.tabs[tab_id]['outline'].destroy()

            self.notebook.forget(self.tabs[tab_id]['frame'])
            del self.tabs[tab_id]

        # 创建新的空白标签页
        self.new_tab()

    def has_unsaved_changes(self, tab_id):
        """检查标签页是否有未保存的更改"""
        tab_info = self.tabs.get(tab_id)
        if not tab_info:
            return False

        if tab_info['file_path']:
            # 如果文件存在，比较内容
            try:
                with open(tab_info['file_path'], 'r', encoding='utf-8') as file:
                    saved_content = file.read()
                current_content = tab_info['text_area'].get(1.0, tk.END)[:-1]  # [:-1]去除最后的换行符
                return saved_content != current_content
            except:
                return True
        else:
            # 如果是新文件，检查是否有内容
            return len(tab_info['text_area'].get(1.0, tk.END).strip()) > 0

    def ask_save_changes(self):
        answer = messagebox.askyesnocancel(
            "保存更改",
            "当前文件有未保存的更改，是否保存？"
        )
        if answer is None:  # 用户点击取消
            return False
        elif answer:  # 用户点击是
            self.save_file()
        return True  # 用户点击否或保存成功

    def on_tab_changed(self, event):
        """处理标签页切换事件"""
        # 先隐藏所有大纲视图
        for tab_info in self.tabs.values():
            if 'outline' in tab_info:
                try:
                    tab_info['outline'].pack_forget()
                except:
                    pass  # 忽略已经被销毁的组件

        # 显示当前标签页的大纲视图
        tab_id, tab_info = self.get_current_tab()
        if tab_id is not None and 'outline' in tab_info:
            try:
                # 显示当前标签页的大纲视图
                tab_info['outline'].pack(fill=tk.BOTH, expand=True, padx=2, pady=2)
                # 更新大纲内容
                tab_info['outline'].update_outline()
            except:
                pass  # 忽略可能的错误

    def handle_key_event(self, event, text_area):
        """处理按键事件，优化撤销操作"""
        current_time = event.time

        # 取消之前的分隔符添加（如果存在）
        if text_area.edit_separator_pending:
            text_area.after_cancel(text_area.edit_separator_pending)

        # 如果距离上次编辑时间超过阈值，添加分隔符
        if current_time - text_area.last_edit_time > self.compound_undo_delay:
            text_area.edit_separator()

        # 更新上次编辑时间
        text_area.last_edit_time = current_time

        # 设置定时器，在一段时间后添加分隔符
        text_area.edit_separator_pending = text_area.after(
            self.compound_undo_delay,
            lambda: self.add_undo_separator(text_area)
        )

    def handle_control_key(self, event, text_area):
        """处理控制键事件"""
        if event.keysym in ('z', 'y'):  # Ctrl+Z 或 Ctrl+Y
            text_area.edit_separator()  # 确保撤销/重做操作前有分隔符

    def add_undo_separator(self, text_area):
        """添加撤销分隔符"""
        text_area.edit_separator()
        text_area.edit_separator_pending = None

    def show_file_tree_menu(self, event):
        """显示文件树右键菜单"""
        # 获取点击的项
        item = self.file_tree.identify_row(event.y)
        if item:
            # 选中被点击的项
            self.file_tree.selection_set(item)
            # 显示菜单
            self.file_tree_menu.tk_popup(event.x_root, event.y_root)

    def delete_selected_file(self):
        """删除选中的文件或文件夹"""
        selected = self.file_tree.selection()
        if not selected:
            return

        # 获取文件路径
        file_path = self.get_item_path(selected[0])

        # 确认是文件还是文件夹
        is_dir = os.path.isdir(file_path)
        item_type = "文件夹" if is_dir else "文件"

        # 显示确认对话框
        if messagebox.askyesno("确认删除",
                             f"确定要删除这个{item_type}吗？\n{file_path}"):
            try:
                if is_dir:
                    import shutil
                    shutil.rmtree(file_path)  # 递归删除文件夹及其内容
                else:
                    os.remove(file_path)  # 删除文件

                # 检查是否有打开的标签页正在编辑这个文件
                for tab_id, tab_info in self.tabs.items():
                    if tab_info['file_path'] == file_path:
                        # 将文件路径设为None，这样关闭时就不会提示保存
                        tab_info['file_path'] = None
                        # 更新标签页标题
                        self.notebook.tab(tab_info['frame'], text="未命名")

                # 刷新文件树
                self.update_file_tree()
                messagebox.showinfo("成功", f"{item_type}已删除")
            except Exception as e:
                messagebox.showerror("错误", f"删除{item_type}失败: {str(e)}")

    def handle_tab(self, event):
        """处理Tab键事件"""
        # 阻止默认的Tab键行为
        event.widget.tk_focusNext().focus()
        return "break"

    def indent_selected_lines(self, event=None):
        """缩进选中的行"""
        text_widget = self.get_current_text_widget()
        if not text_widget:
            return "break"

        try:
            # 获取选中的范围
            if text_widget.tag_ranges("sel"):
                start_line = int(float(text_widget.index("sel.first")))
                end_line = int(float(text_widget.index("sel.last")))

                # 如果选择结束于行首，不处理最后一行
                if text_widget.index("sel.last").endswith(".0"):
                    end_line -= 1
            else:
                # 如果没有选中文本，处理当前行
                start_line = int(float(text_widget.index("insert")))
                end_line = start_line

            # 为每行添加缩进
            for line in range(start_line, end_line + 1):
                text_widget.insert(f"{line}.0", "    ")

        except Exception as e:
            print(f"缩进错误: {str(e)}")

        return "break"  # 防止默认的Tab键行为

    def on_text_modified(self, event):
        """处理文本修改事件"""
        text_widget = event.widget
        text_widget.edit_modified(False)  # 重置修改标志

        # 获取当前标签页信息
        tab_id, tab_info = self.get_current_tab()
        if tab_id is not None:
            # 获取当前标签名
            current_title = self.notebook.tab(tab_info['frame'], "text")
            if not current_title.startswith("*"):
                # 检查是否真的有未保存的更改
                if self.has_unsaved_changes(tab_id):
                    # 添加*号
                    base_title = current_title
                    self.notebook.tab(tab_info['frame'], text="*" + base_title)
                    # 更新标签页样式
                    tab_info['frame'].configure(style='Custom.TNotebook.Tab')

    def handle_save(self, event):
        """处理保存操作"""
        self.save_file()
        return "break"

    def handle_undo(self, event):
        """处理撤销操作"""
        try:
            event.widget.edit_undo()
        except tk.TclError:
            pass
        return "break"

    def handle_redo(self, event):
        """处理重做操作"""
        try:
            event.widget.edit_redo()
        except tk.TclError:
            pass
        return "break"

    def handle_open_directory(self, event):
        """处理打开目录操作"""
        self.open_directory()
        return "break"

    def handle_preview(self, event):
        """处理预览切换操作"""
        self.toggle_preview()
        return "break"

if __name__ == "__main__":
    # 使用 ttkbootstrap 创建主窗口
    root = ttk.Window(themename="darkly")
    editor = TextEditor(root)
    root.geometry("1000x700")
    root.mainloop()
